using System;
using System.IO;
using System.Reflection;
using BruteForceSerializer;
using BruteForceSerializer.Contract;
using LinFu.AOP.Interfaces;

namespace ComponentTracer
{
    public class TraceInterceptor : IInterceptor
    {
        private const int BlanksPerIndentationLevel = 2;
        private static int indentationLevel;
        private readonly object target;
        private readonly Action<string> tracer;
        private readonly ISerializer serializer;

        public TraceInterceptor(object target, Action<string> tracer) {
            this.tracer = tracer;
            this.target = target;

            serializer = new Serializer();
        }

        public object Intercept(IInvocationInfo info) {
            TraceMethodCall(info);
            var returnValue = CallOriginalMethod(info);
            TraceReturnValue(returnValue);

            return returnValue;
        }

        private void TraceMethodCall(IInvocationInfo info) {
            tracer(string.Format("{0}{1}({2})\n", Indentation(), MethodName(info), Arguments(info.Arguments)));
        }

        private object CallOriginalMethod(IInvocationInfo info) {
            indentationLevel++;
            object returnValue;
            try {
                returnValue = info.TargetMethod.Invoke(target, info.Arguments);
            }
            catch (TargetInvocationException e) {
                tracer(string.Format("{0}throws exception {1}\n", Indentation(), e.InnerException));
                throw e.InnerException;
            }
            finally {
                indentationLevel--;
            }
            return returnValue;
        }

        private void TraceReturnValue(object returnValue) {
            if (returnValue != null) {
                tracer(string.Format("{0}returns {1}\n", Indentation(), Arguments(new[] {returnValue})));
            }
        }

        private static string Indentation() {
            return new string(' ', indentationLevel * BlanksPerIndentationLevel);
        }

        private string MethodName(IInvocationInfo info) {
            return string.Format("{0}.{1}", target.GetType().FullName, info.TargetMethod.Name);
        }

        private string Arguments(object[] arguments) {
            using (var stringWriter = new StringWriter()) {
                foreach (var arg in arguments) {
                    stringWriter.WriteLine(serializer.Serialize(arg));
                }
                return stringWriter.ToString();
            }
        }
    }
}